package uk.co.caprica.vlcj.oop.registry;

import static uk.co.caprica.vlcj.oop.registry.ObjectName.REGISTRY;
import static uk.co.caprica.vlcj.oop.registry.ObjectName.RENDEZVOUS;
import gnu.cajo.invoke.Remote;
import gnu.cajo.utils.ItemServer;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import uk.co.caprica.vlcj.logger.Logger;

/**
 * Remoting registry component.
 * <p>
 * With Cajo, only one remoting registry is allowed per virtual machine.
 * <p>
 * Note that the remoting registry <em>must</em> be initialised <em>before</em> 
 * binding any objects via the {@link ItemServer}.
 * <p>
 * <strong>All public methods exposed by this class may be invoked by a remote process.</strong>
 */
public final class RemotingRegistry {

    /**
     * Singleton holder for the registry instance.
     */
    private static final class RemotingRegistryHolder {

        /**
         * Singleton instance.
         */
        private static final RemotingRegistry INSTANCE = new RemotingRegistry();
    }
    
    /**
     * Name of the host on which the remoting server resides.
     */
    private final String host;
    
    /**
     * Port number at which the remoting server resides.
     */
    private final int port;
    
    /**
     * Create an instance of the registry.
     */
    private RemotingRegistry() {
        Logger.debug("RemotingRegistry()");
        // Configure the remoting infrastructure to take the next free port - the
        // server will not be started until at least one object is bound
        Remote.config(null, 0, null, 0);
        // Bind an instance of this object in the registry, starting the server
        try {
            ItemServer.bind(RemotingRegistryHolder.INSTANCE, REGISTRY.objectName());
        }
        catch(IOException e) {
            throw new RuntimeException("Failed to bind", e);
        }
        // Store the host/port of the registry
        host = Remote.getDefaultServerHost();
        Logger.debug("host={}", host);
        port = Remote.getDefaultServerPort();
        Logger.debug("callbackPort={}", port);
    }

    /**
     * Get a reference to the remoting registry singleton.
     * 
     * @return registry
     */
    public static final RemotingRegistry instance() {
        return RemotingRegistryHolder.INSTANCE;
    }
    
    /**
     * Get the name of the host on which the remoting server resides.
     * 
     * @return host name
     */
    public final String getHost() {
        return host;
    }

    /**
     * Get the port number at which the remoting server resides.
     * 
     * @return port number
     */
    public final int getPort() {
        return port;
    }
    
    /**
     * 
     * 
     * @param object
     * @param name
     * @throws IOException 
     */
    public final void bind(Object object, String name) throws IOException {
        Logger.debug("bind(object={},name={})", object, name);
        ItemServer.bind(object, name);
    }

    /**
     * List of all the remote process objects.
     * 
     * @return remote processes
     */
    public final List<String> processes() {
        String[] names = ItemServer.list();
        List<String> result = new ArrayList<String>(names.length);
        for(String name : names) {
            if(name.startsWith(RENDEZVOUS.objectName())) {
                result.add(name);
            }
        }
        return result;
    }

    /**
     * Release the resources associated with this component.
     */
    public final void release() {
        // FIXME
    }
    
    /**
     * Get the name for a remote object.
     * 
     * @param host host on which the object resides
     * @param port port at which the object resides
     * @param name name of the object
     * @return remote object
     */
    public static final String objectName(String host, int port, String name) {
        Logger.trace("objectName(host={},port={},name={}", host, port, name);
        StringBuilder sb = new StringBuilder(40);
        sb.append("//").append(host).append(':').append(port).append('/').append(name);
        String objectName = sb.toString();
        Logger.trace("objectName={}", objectName);
        return objectName;
    }
}
